/*
 *
 * DX_SourceOutliner
 * Copyright (C)2009
 * http://code.google.com/p/dxcsourceoutliner
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

using System;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using DevExpress.CodeRush.Core;
using DevExpress.CodeRush.PlugInCore;
using DevExpress.CodeRush.StructuralParser;
using System.Diagnostics;
using System.Collections.Generic;

namespace DX_SourceOutliner
{
    [Title("DX Source Outliner")]
    public partial class SourceOutlinerToolWindow : ToolWindowPlugIn
    {
        private bool _clickHandlerActive;

        private SourceCodeEditor _editor;

        private ElementTreeBuilder _elementTree;

        private bool _expandCollapseInProgress;

        private bool _handleEvents;

        private bool _isEditorIdle;

        private bool _isTreeRenderedCurrent;

        private NodeFilter _nodeFilter;

        private TreeNodeProcessor _nodeProcessor;

        private TreeNode _selectedNode;

        private bool _showAllOpenDocumentsInTree;

        public override void FinalizePlugIn()
        {
            base.FinalizePlugIn();
        }

        public override void InitializePlugIn()
        {
            base.InitializePlugIn();

            SetInitialControlState();

            _elementTree = new ElementTreeBuilder()
            {
                IncludeParameterNamesInMethods = chkbtnShowMethodParameterNames.Checked,
                IncludeReturnTypesInMethods = chkbtnShowMethodReturnTypes.Checked
            };

            _editor = new SourceCodeEditor(locatorBeacon);
            _nodeProcessor = new TreeNodeProcessor();
            _nodeFilter = new NodeFilter();

            _isEditorIdle = false;
            _isTreeRenderedCurrent = false;
            _handleEvents = true;

            RenderTree();
        }

        private void btnFiltersVisibleToggle_Click(object sender, EventArgs e)
        {
            pnlFilters.Visible = !pnlFilters.Visible;

            btnFiltersVisibleToggle.ImageKey = pnlFilters.Visible ? "Panel_Collapse_UP_Arrow" : "Panel_Collapse_DOWN_Arrow";
        }

        private void chkShowAllDocumentsInTree_CheckedChanged(object sender, EventArgs e)
        {
            _showAllOpenDocumentsInTree = chkShowAllDocumentsInTree.Checked;

            if (_handleEvents)
                ReRenderTree();
        }

        private bool CheckNodeShouldBeVisible(LanguageElement element)
        {
            return (CheckNodeVisibilityByElementType(element) && CheckNodeVisibilityByAccessibility(element));
        }

        private bool CheckNodeVisibilityByAccessibility(LanguageElement element)
        {
            IMemberElement member = element as IMemberElement;

            if (member == null) return true;

            switch (member.Visibility)
            {
                case MemberVisibility.Public:
                    return chkbtnShowPublicMembers.Checked;

                case MemberVisibility.Protected:
                    return chkbtnShowProtectedMembers.Checked;

                case MemberVisibility.Private:
                    return chkbtnShowPrivateMembers.Checked;

                case MemberVisibility.Internal:
                    return chkbtnShowInternalMembers.Checked;

                case MemberVisibility.ProtectedInternal:
                    return chkbtnShowProtectedInternalMembers.Checked;

                default:
                    return false;

            }

        }

        private bool CheckNodeVisibilityByElementType(LanguageElement element)
        {
            if (element == null) return true;

            switch (element.ElementType)
            {
                case LanguageElementType.Method:
                    return chkbtnShowMethods.Checked;

                case LanguageElementType.Property:
                    return chkbtnShowProperties.Checked;

                case LanguageElementType.Event:
                    return chkbtnShowEvents.Checked;

                case LanguageElementType.Variable:
                case LanguageElementType.InitializedVariable:
                    return chkbtnShowVariables.Checked;

                case LanguageElementType.Class:
                    return chkbtnShowClasses.Checked;

                case LanguageElementType.Namespace:
                    return chkbtnShowNamespaces.Checked;

                case LanguageElementType.Interface:
                    return chkbtnShowInterfaces.Checked;

                case LanguageElementType.Delegate:
                    return chkbtnShowDelegates.Checked;

                case LanguageElementType.Enum:
                case LanguageElementType.EnumElement:
                    return chkbtnShowEnums.Checked;

                case LanguageElementType.Struct:
                    return chkbtnShowStructs.Checked;

                default:
                    return true;
            }
        }

        private void chkbtnShowAllAccess_CheckedChanged(object sender, EventArgs e)
        {
            _handleEvents = false;

            bool showAllChecked = chkbtnShowAllAccess.Checked;

            chkbtnShowPrivateMembers.Checked = showAllChecked;
            chkbtnShowProtectedMembers.Checked = showAllChecked;
            chkbtnShowInternalMembers.Checked = showAllChecked;
            chkbtnShowProtectedInternalMembers.Checked = showAllChecked;
            chkbtnShowPublicMembers.Checked = showAllChecked;

            _handleEvents = true;

            ReRenderTree();
        }

        private void chkbtnShowAllMembers_CheckedChanged(object sender, EventArgs e)
        {
            _handleEvents = false;

            bool showAllChecked = chkbtnShowAllMembers.Checked;

            chkbtnShowNamespaces.Checked = showAllChecked;
            chkbtnShowMethods.Checked = showAllChecked;
            chkbtnShowProperties.Checked = showAllChecked;
            chkbtnShowEvents.Checked = showAllChecked;
            chkbtnShowVariables.Checked = showAllChecked;

            _handleEvents = true;

            ReRenderTree();
        }

        private void chkbtnShowAllTypes_CheckedChanged(object sender, EventArgs e)
        {
            _handleEvents = false;

            bool showAllChecked = chkbtnShowAllTypes.Checked;

            chkbtnShowClasses.Checked = showAllChecked;
            chkbtnShowInterfaces.Checked = showAllChecked;
            chkbtnShowStructs.Checked = showAllChecked;
            chkbtnShowEnums.Checked = showAllChecked;
            chkbtnShowDelegates.Checked = showAllChecked;

            _handleEvents = true;

            ReRenderTree();
        }

        private void chkbtnShowClasses_CheckedChanged(object sender, EventArgs e)
        {
            if (_handleEvents)
                ReRenderTree();
        }

        private void chkbtnShowDelegates_CheckedChanged(object sender, EventArgs e)
        {
            if (_handleEvents)
                ReRenderTree();
        }

        private void chkbtnShowEnums_CheckedChanged(object sender, EventArgs e)
        {
            if (_handleEvents)
                ReRenderTree();
        }

        private void chkbtnShowEvents_CheckedChanged(object sender, EventArgs e)
        {
            if (_handleEvents)
                ReRenderTree();
        }

        private void chkbtnShowInterfaces_CheckedChanged(object sender, EventArgs e)
        {
            if (_handleEvents)
                ReRenderTree();
        }

        private void chkbtnShowInternalMembers_CheckedChanged(object sender, EventArgs e)
        {
            if (_handleEvents)
                ReRenderTree();
        }

        private void chkbtnShowMethodParameterNames_CheckedChanged(object sender, EventArgs e)
        {
            _elementTree.IncludeParameterNamesInMethods = chkbtnShowMethodParameterNames.Checked;
            ReRenderTree();
        }

        private void chkbtnShowMethodReturnTypes_CheckedChanged(object sender, EventArgs e)
        {
            _elementTree.IncludeReturnTypesInMethods = chkbtnShowMethodReturnTypes.Checked;
            ReRenderTree();
        }

        private void chkbtnShowMethods_CheckedChanged(object sender, EventArgs e)
        {
            if (_handleEvents)
                ReRenderTree();
        }

        private void chkbtnShowNamespaces_CheckedChanged(object sender, EventArgs e)
        {
            if (_handleEvents)
                ReRenderTree();
        }

        private void chkbtnShowPrivateMembers_CheckedChanged(object sender, EventArgs e)
        {
            if (_handleEvents)
                ReRenderTree();
        }

        private void chkbtnShowProperties_CheckedChanged(object sender, EventArgs e)
        {
            if (_handleEvents)
                ReRenderTree();
        }

        private void chkbtnShowProtectedInternalMembers_CheckedChanged(object sender, EventArgs e)
        {
            if (_handleEvents)
                ReRenderTree();
        }

        private void chkbtnShowProtectedMembers_CheckedChanged(object sender, EventArgs e)
        {
            if (_handleEvents)
                ReRenderTree();
        }

        private void chkbtnShowPublicMembers_CheckedChanged(object sender, EventArgs e)
        {
            if (_handleEvents)
                ReRenderTree();
        }

        private void chkbtnShowStructs_CheckedChanged(object sender, EventArgs e)
        {
            if (_handleEvents)
                ReRenderTree();
        }

        private void chkbtnShowVariables_CheckedChanged(object sender, EventArgs e)
        {
            if (_handleEvents)
                ReRenderTree();
        }

        private void chkbtnSortAphabetical_CheckedChanged(object sender, EventArgs e)
        {
            _nodeFilter.SortAlphabetically = chkbtnSortAphabetical.Checked;

            ReRenderTreeWithFilterApplied();
        }

        private void chkFilterActive_CheckedChanged(object sender, EventArgs e)
        {
            ToggleFilterControlsEnabled();

            if (chkFilterActive.Checked)
                ReRenderTreeWithFilterApplied();
            else
                ReRenderTree();

        }

        private void ClearTree()
        {
            if (tvElements.SelectedNode != null)
                _selectedNode = tvElements.SelectedNode;

            tvElements.Nodes.Clear();

        }

        private void events_AfterParse(AfterParseEventArgs ea)
        {
            Debug.WriteLine("AfterParse Event raised...");

            _isTreeRenderedCurrent = false;

            if (!_isEditorIdle)
                return;

            ClearTree();
            RenderTree(ea.TextDocument);
            HighLightActiveElementInTree();
        }

        private void events_CaretMoved(CaretMovedEventArgs ea)
        {
            Debug.WriteLine("CaretMoved Event raised...");

            if (!_isEditorIdle)
                return;

            HighLightActiveElementInTree();
        }

        private void events_DocumentActivated(DocumentEventArgs ea)
        {
            Debug.WriteLine("DocumentActivated Event raised...");

            if (!_clickHandlerActive)
            {
                _isTreeRenderedCurrent = false;
                ClearTree();
                RenderTree(ea);
            }
            HighLightActiveElementInTree();
        }

        private void events_DocumentClosing(DocumentEventArgs ea)
        {
            ClearTree();
        }

        private void events_DocumentOpened(DocumentEventArgs ea)
        {
            Debug.WriteLine("DocumentOpened Event raised...");

            _isTreeRenderedCurrent = false;

            ClearTree();
            RenderTree(ea);
            HighLightActiveElementInTree();
        }

        private void events_EditorIdle(EditorIdleEventArgs ea)
        {
            //if (ea.MillisecondsIdle > 500)
            _isEditorIdle = true;

            if (_isTreeRenderedCurrent == false)
                ReRenderTree();
        }

        private void HighLightActiveElementInTree()
        {
            LanguageElement currentElement = CodeRush.Source.CodeActive;

            IEnumerable<TreeNode> nodes = _nodeProcessor.GetAllNodes(tvElements.Nodes);

            foreach (TreeNode node in nodes)
            {
                if ((node.Tag as LanguageElement) == currentElement)
                {
                    tvElements.SelectedNode = node;
                }
            }

            if (tvElements.SelectedNode != null)
                tvElements.SelectedNode.EnsureVisible();
        }

        private void RenderTree()
        {
            var doc = CodeRush.Documents.ActiveTextDocument as TextDocument;
            if (doc != null)
            {
                RenderTree(doc);
            }
        }

        private void RenderTree(TextDocument textDocument)
        {
            _isEditorIdle = false;

            if (_showAllOpenDocumentsInTree)
            {
                foreach (TextDocument document in CodeRush.Documents.AllTextDocuments)
                {
                    TreeNode parentNode = _elementTree.CreateDocumentNode(document);

                    tvElements.Nodes.Add(parentNode);
                    RenderTreeForSingleDocument(document, parentNode);
                }
            }
            else
                RenderTreeForSingleDocument(textDocument, null);

            _isTreeRenderedCurrent = true;
        }

        private void RenderTree(DocumentEventArgs ea)
        {
            var doc = ea.Document as TextDocument;
            if (doc != null)
            {
                if (chkFilterActive.Checked)
                    ReRenderTreeWithFilterApplied();
                else
                    RenderTree(doc);
            }
        }

        private void RenderTreeForSingleDocument(TextDocument textDocument, TreeNode parentNode)
        {
            if (chkFilterActive.Checked)
            {
                foreach (TreeNode node in _nodeProcessor.GetAllNodesWithChildren(_elementTree.GenerateTreeNodes(textDocument)))
                {
                    node.Nodes.Clear();

                    if (CheckNodeShouldBeVisible(node.Tag as LanguageElement))
                        if (!tvElements.Nodes.Contains(node))
                            tvElements.Nodes.Add(node);
                }

            }
            else
            {
                //if (parentNode != null && !tvElements.Nodes.Contains(parentNode))
                //    tvElements.Nodes.Add(parentNode);

                foreach (TreeNode node in _elementTree.GenerateTreeNodes(textDocument))
                {
                    if (!tvElements.Nodes.Contains(node))
                    {
                        if (parentNode != null)
                            tvElements.Nodes.Find(parentNode.Name, true).FirstOrDefault().Nodes.Add(node);
                        else
                            tvElements.Nodes.Add(node);
                    }
                }
            }

            tvElements.ExpandAll();

            if (_selectedNode != null)
                tvElements.SelectedNode = tvElements.Nodes.Find(_selectedNode.Name, true).FirstOrDefault();
        }

        private void ReRenderTree()
        {
            ClearTree();

            if (chkFilterActive.Checked)
                ReRenderTreeWithFilterApplied();
            else
                RenderTree();

            HighLightActiveElementInTree();
        }

        private void ReRenderTreeWithFilterApplied()
        {
            ClearTree();
            RenderTree();

            tvElements.BeginUpdate();

            string filterPattern = chkFilterActive.Checked ? txtFilterPattern.Text : string.Empty;

            List<TreeNode> matchingNodes = _nodeFilter.GetMatchingNodes(tvElements.Nodes, filterPattern);

            ClearTree();

            matchingNodes.ForEach(node => tvElements.Nodes.Add(node));

            tvElements.EndUpdate();
            HighLightActiveElementInTree();
        }

        private void SetInitialControlState()
        {
            pnlFilters.Visible = false;
            chkFilterActive.Checked = false;
            ToggleFilterControlsEnabled();
            chkbtnShowMethodParameterNames.Checked = false;
            chkbtnShowMethodReturnTypes.Checked = false;
            chkbtnSortAphabetical.Checked = false;
            chkShowAllDocumentsInTree.Checked = false;
        }

        private void ToggleFilterControlsEnabled()
        {
            txtFilterPattern.Enabled = chkFilterActive.Checked;

            foreach (Control control in pnlFilterToggles.Controls)
            {
                control.Enabled = chkFilterActive.Checked;
            }
        }

        private void tvElements_BeforeCollapse(object sender, TreeViewCancelEventArgs e)
        {
            _expandCollapseInProgress = true;
        }

        private void tvElements_BeforeExpand(object sender, TreeViewCancelEventArgs e)
        {
            _expandCollapseInProgress = true;
        }

        private void tvElements_Click(object sender, EventArgs e)
        {
            if (_expandCollapseInProgress)
            {
                _expandCollapseInProgress = false;
                return;
            }

            _clickHandlerActive = true;

            tvElements.SelectedNode = null;
            MouseEventArgs mouseArgs = e as MouseEventArgs;

            if (mouseArgs == null)
                return;

            _selectedNode = tvElements.GetNodeAt(new Point(mouseArgs.X, mouseArgs.Y));
            LanguageElement selectedElement = _selectedNode.Tag as LanguageElement;
            _editor.NavigateToSelectedElement(selectedElement, true);

            tvElements.SelectedNode = tvElements.Nodes.Find(_selectedNode.Name, true).FirstOrDefault();

            Focus();

            _clickHandlerActive = false;
            _expandCollapseInProgress = false;

        }

        private void tvElements_DoubleClick(object sender, EventArgs e)
        {
            _clickHandlerActive = true;

            if (tvElements.SelectedNode != null)
                _editor.NavigateToSelectedElement(tvElements.SelectedNode.Tag as LanguageElement, false);

            _clickHandlerActive = false;

        }

        private void txtFilterPattern_TextChanged(object sender, EventArgs e)
        {
            ReRenderTreeWithFilterApplied();
        }

    }
}